/**
 * @file invoker.c
 * @brief Source File creato per gestire la registrazione e l'escuzione dei comandi lanciati dal Client.
 *
 * Nell'esecuzione è responsabile di:
 *      - registrazione dei possibili comandi da lanciare
 *      - memorizzazione e centralizzazione delle richieste ricevute
 *      - controllo dello stato del programma
 *
 * @date Feb 26, 2012
 * @author Francesco Corvino
 */

#include <stdlib.h>
#include <string.h>
#include <pthread.h>				/** Libreria utilizzata per la gestione delle threads */
#include "../MemObj/dynArray.h"
#include "../MemObj/hiddenType.h"
#include "../RscHost/session.h"

#include "invoker.h"					/// interfaccia invoker
#include "inputHandler.h"				/// necessaria per comunicare con InputHandler
#include "../RscHost/rscApplication.h"	/// utilizzata per gestire comandi e richieste
#include "../logproxy/logproxy.h"		/// si utilizza il logging

static Invoker objInvoker;

static pthread_t mythread;

InterfaceCommand * _invoke_getCMD(char *name){
	DynArray *commands;InterfaceCommand *comm=NULL;
	commands = RscApp_getCommands();
	comm = Command_get(commands,name);
	if(comm==NULL) {
		log_namedlevel(LOG_NAMEINVOKER, LOG_INFO_LEVEL,
				"Comando <%s> non trovato!",name);
		return Command_getDefault();
	}
	return comm;
}

void _invoke_addReq(UserRequest *req){
	RscApp_updateRequest(req,req->session->keySession);
}

/** @brief Viene incaricato di gestire inizio e terminazione della thread.
 *
 * L'invoker effettua una chiamata alla funzione manage per gestire la creazione
 * della thread, nella creazione si richiama questa funzione che incapsula
 * l'esecuzione del comando richiamato.
 *
 * @todo aggiungere codice per monitorare il tempo di esecuzione dei comandi.
 *
 * @see _invoke_manageCommandLaunch()
 */
void *_invoke_incapsulationCommandLaunch(void *reqtopass){
	UserRequest *req = (UserRequest*) reqtopass;
	InterfaceCommand *comd = req->command;

	log_namedlevel(LOG_NAMEINVOKER, LOG_DEBUG_LEVEL,
		"Avviata elaborazione.. il mio tid: %d",pthread_self());

	Session_addMyElaboration(req->session);	/// aggiunge l'elaborazione alla sessione

	/// - aggiorna la sessione.
	Session_Update(req->session);

	/// - imposta la sessione nello stato di occupata
	req->session->isWaiting = 2;

	/**
	 * esegue il comando principale richiamando il puntatore a funzione specificato
	 * nell'intefaccia dei comandi.
	 */
	(*comd->execute)();

	/**
	 * Una volta eseguito il comando effettua le operazioni di chiusura della thread:
	 */

	/// - imposta la sessione nello stato di attesa di input
	req->session->isWaiting = 0;

	/// - indica di rimuovere l'elaborazione dalla sessione
	Session_delMyElaboration(req->session);

	/// - indica di aggiornare le informazioni sull'input dell'utente all'InputHandler
	IH_updateInput();

	log_namedlevel(LOG_NAMEINVOKER,LOG_INFO_LEVEL,"fine escuzione del comando");
	pthread_exit("Thank you for the CPU time");
	return NULL;
}

void _invoke_controlThreads(HostSession* session,void*args){
	log_namedlevel(LOG_NAMEINVOKER,LOG_DEBUG_LEVEL,
			"Richiesto aggiornamento della sessione %d",session->keySession);
	Session_delElaborationFor(session,mythread);
}

/**
 * @brief Gestisce l'invocazione di una thread per lanciare il programma.
 *
 * In questa funzione si possono gestire le operazioni di invocazione delle thread
 * che eseguiranno il comando.
 *
 * @param cmd puntatore al comando da lanciare
 * @param req richiesta di cui gestire il lancio del comando
 */
void _invoke_manageCommandLaunch(InterfaceCommand * cmd, UserRequest * req){
	pthread_t invokedcomm;
	/**
	 * Al momento le operazioni eseguite prima dell'avvio della thread
	 * per gestirne l'escuzione sono:
	 */

	req->command = cmd;		/// - completa i dati della richiesta in memoria

	_invoke_addReq(req);	/// - registra la richiesta appena effettuata

	/// aggiunge la stessa main thread alla sessione per sincronizzarsi
	ElaborationEntity controlElab;
	controlElab.keyElaboration = mythread;
	controlElab.notify = _invoke_controlThreads;
	controlElab.typeOfElaboration = 0;
	Session_addElaboration(req->session,&controlElab);

	/// - crea una thread per il comando
	int res = pthread_create( &invokedcomm, NULL,
			_invoke_incapsulationCommandLaunch,
			(void*) req);

	/**
	 * Una volta avviata la thread la funzione controlla
	 * se la thread non è stata invocata.
	 */
	if(res==-1) {
		/**
		 *  in questo caso vanno inserite le operazioni di segnalazione
		 *  e di ripristino dei dati, che sono:
		 */

		/// - segnala l'errore nel log
		log_namedlevel(LOG_NAMEINVOKER,
			LOG_DEBUG_LEVEL, "errore nell'invocazione del comando");

		/// - rimuove l'elaborazione di controllo
		Session_delMyElaboration(req->session);

	} else {
		//while(req->session->isWaiting==1){}
		log_namedlevel(LOG_NAMEINVOKER, LOG_DEBUG_LEVEL,
			"Avviata elaborazione sulla thread con tid %d",invokedcomm);

	}

}

/**
 * @brief Setter delle configurazioni dell'invoker.
 */
void Invoke_setInvoker(Invoker *value){
	memcpy(&objInvoker, value,sizeof(Invoker));
	mythread = pthread_self();
}

/**
 * @brief Funzione principale dell'entità invoker.
 *
 * Permette di eseguire i comandi partendo da un oggetto UserRequest che caratterizza
 * il tipo di richiesta effettuata dall'utente.
 *
 * @param req puntatore a un'oggetto UserRequest che contiene tutte le informazioni sull'input dell'utente.
 */
void Invoke_invokeCMD(UserRequest * req){
	log_namedlevel(LOG_NAMEINVOKER,LOG_DEBUG_LEVEL,
			"Ricava il comando <%s> da eseguire",req->nameCmd);
	InterfaceCommand *comm = _invoke_getCMD(req->nameCmd);
	//TODO da implementare se è necessario eseguire funzioni history & undo
	log_namedlevel(LOG_NAMEINVOKER,LOG_DEBUG_LEVEL,"Invocazione del comando <%s>",req->nameCmd);
	_invoke_manageCommandLaunch(comm,req);

}
